package icraf.carbon.gui.chart;

import icraf.carbon.gui.MenuFactory.MenuProperty;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.text.DecimalFormat;
import java.text.NumberFormat;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class ChartCanvas extends JPanel {
	public static int NORMAL_SCALE = 0, LOG_SCALE = 1;
	private static final long serialVersionUID = -5690198666688245115L;

	public static void main(String[] args) {

		final JFrame frame = new JFrame();
		ChartCanvas mainPanel = new ChartCanvas();
		mainPanel.setPreferredSize(new Dimension(400, 300));

		mainPanel.setRangeX(-4, 12);
		mainPanel.setTitleX("C-stock");
		mainPanel.setRangeY(0, 12280);
		mainPanel.setTitleY("Cost");

		frame.getContentPane().add(mainPanel, BorderLayout.CENTER);
		frame.pack();
		frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				frame.setVisible(true);
			}
		});

	}

	CanvasFormat canvasSetting = new CanvasFormat();
	private DecimalFormat formatterX = (DecimalFormat)NumberFormat.getNumberInstance();
	private DecimalFormat formatterY = (DecimalFormat)NumberFormat.getNumberInstance();
	private double maxPowerY = 8;
	private double maxX = 10;
	private double maxY = 10;
	private double minX = 0;
	private double minY = 0;
	private int nUnitX = 10;
	private int nUnitY = 10;

	private int scaleTypeX = NORMAL_SCALE;
	private int scaleTypeY = LOG_SCALE;

	private double scaleX = 1;
	private double scaleY = 1;
	private boolean showNegativeY = true;
	private boolean showPositiveY = true;
	private int sizeAxisTick = 5;
	private int sizeBorderBottom = 50;
	private int sizeBorderLeft = 50;
	private int sizeBorderRight = 50;

	private int sizeBorderTop = 10;
	// private int sizeGridLine = 1;
	private String titleX = "X Title";
	private String titleY = "Y Title";

	public ChartCanvas() {
		formatterY.setMaximumFractionDigits(10);
		formatterX.setMaximumFractionDigits(10);
	}

	int getCanvasHeight() {
		return getHeight() - sizeBorderTop - sizeBorderBottom;
	}

	public CanvasFormat getCanvasSetting() {
		return canvasSetting;
	}

	int getCanvasWidth() {
		return getWidth() - sizeBorderLeft - sizeBorderRight;
	}

	int getCanvasX() {
		return sizeBorderLeft;
	}

	int getCanvasY() {
		return sizeBorderTop;
	}

	public double getMaxPowerY() {
		return maxPowerY;
	}

	public double getMaxX() {
		return maxX;
	}

	public double getMaxY() {
		return maxY;
	}

	public double getMinX() {
		return minX;
	}

	public double getMinY() {
		return minY;
	}

	public int getnUnitY() {
		return nUnitY;
	}

	public Rectangle getPaintCanvasArea(Graphics2D g2) {
		updateBorderSize(g2);
		Dimension d = getSize();
		Rectangle r = new Rectangle(sizeBorderLeft, sizeBorderTop, d.width
				- sizeBorderLeft - sizeBorderRight, d.height - sizeBorderTop
				- sizeBorderBottom);
		return r;
	}

	public int getPixelX(double x) {
		int px1 = sizeBorderLeft;
		int px2 = getWidth() - sizeBorderRight;
		double deltaX = Math.abs(maxX - minX);
		int xPix = px1 + (int) Math.round((px2 - px1) * (x - minX) / deltaX);
		return xPix;
	}

	public int getPixelY(double y) {
		double canvasHeight = getCanvasHeight();
		double scaleDoubleY = isShowNegativeLogY() && isShowPositiveY() ? 2 : 1;
		if (y == 0) {
			if (isShowPositiveY())
				return (int) (getCanvasY() + canvasHeight / scaleDoubleY);
			return getCanvasY();
		}
		if (scaleTypeY == LOG_SCALE) {
			// delta of power below and above 0 -> y above and below 1
			double deltaY = this.getMaxPowerY() * 2;
			// doubled if negative log shown
			// scaled from half of the height
			double scaleY = canvasHeight / (scaleDoubleY * (deltaY + 1));
			double logY = Math.log10(Math.abs(y));
			double hPix = (logY + getMaxPowerY() + 1) * scaleY;

			double yPix = getCanvasY() + canvasHeight / scaleDoubleY - hPix;
			return (int) Math.round(yPix);
		} else {
			double maxScale = maxY;
			if (scaleDoubleY == 1 && isShowNegativeLogY()) {
				maxScale = Math.abs(minY);
			}
			double scaleY = canvasHeight / (scaleDoubleY * maxScale);

			double hPix = Math.abs(y) * scaleY;

			double yPix = getCanvasY() + canvasHeight / scaleDoubleY - hPix;
			return (int) Math.round(yPix);
		}

	}

	public int getScaleTypeX() {
		return scaleTypeX;
	}

	public int getScaleTypeY() {
		return scaleTypeY;
	}

	public String getTitleX() {
		return titleX;
	}

	public double getValueY(int yPix) {
		double canvasHeight = getCanvasHeight();
		double scaleDoubleY = isShowNegativeLogY() && isShowPositiveY() ? 2 : 1;
		if (scaleTypeY == LOG_SCALE) {
			double hMax = canvasHeight / scaleDoubleY;
			double y = ((getMaxPowerY() * 2 + 1)
					* (hMax - yPix + sizeBorderTop) / hMax)
					- 1 - getMaxPowerY();
			return Math.pow(10, y);
		} else {
			double hMax = canvasHeight / scaleDoubleY;
			double scaleY = hMax / maxY;
			return (hMax - yPix + sizeBorderTop) / scaleY;
		}
	}

	public boolean isInsideBottomBorder(Point p) {
		if (p.x > sizeBorderLeft && p.x < (this.getWidth() - sizeBorderRight)
				&& p.y > (this.getHeight() - sizeBorderBottom))
			return true;
		return false;
	}

	public boolean isInsideLeftBorder(Point p) {
		if (p.x < sizeBorderLeft && p.y > sizeBorderTop
				&& p.y < (this.getHeight() - sizeBorderBottom))
			return true;
		return false;
	}

	public boolean isShowNegativeLogY() {
		return showNegativeY;
	}

	// private void paintAxisExtra(Graphics2D g2) {
	// if(minY != 0) {
	// int pixY = getPixelY(0);
	// g2.setColor(canvasSetting.xAxisExtra.lineColor);
	// g2.setColor(Color.red);
	// g2.setStroke(canvasSetting.xAxisExtra.generateStroke());
	// g2.drawLine(sizeBorderLeft, pixY, sizeBorderRight, pixY);
	// }
	// if(minX != 0) {
	// int pixX = getPixelX(0);
	// g2.setColor(canvasSetting.xAxisExtra.lineColor);
	// g2.setColor(Color.red);
	// g2.setStroke(canvasSetting.xAxisExtra.generateStroke());
	// g2.drawLine(pixX, sizeBorderTop, pixX, sizeBorderBottom);
	// }
	//
	// }

	public boolean isShowPositiveY() {
		return showPositiveY;
	}

	@Override
	public void paint(Graphics g) {
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		Dimension d = getSize();
		g2.setColor(canvasSetting.background.fillColor);
		g2.fillRect(0, 0, d.width, d.height);
		updateBorderSize(g2);

		paintAxisX(g2);
		paintAxisY(g2);
		// paintAxisExtra(g2);

		paintCanvas(g2, sizeBorderLeft, sizeBorderTop, d.width - sizeBorderLeft
				- sizeBorderRight, d.height - sizeBorderTop - sizeBorderBottom);
	}

	private void paintAxisX(Graphics2D g2) {
		FontMetrics fmUnitX = g2.getFontMetrics(canvasSetting.xUnit.font);
		FontMetrics fmTitleX = g2.getFontMetrics(canvasSetting.xAxis.font);

		Dimension d = getSize();
		int px1 = sizeBorderLeft;
		int py1 = d.height - sizeBorderBottom;
		int px2 = d.width - sizeBorderRight;
		int py2 = py1;

		// paint the axis
		g2.setColor(canvasSetting.xAxis.lineColor);
		g2.setStroke(canvasSetting.xAxis.generateStroke());
		g2.drawLine(px1, py1, px2, py2);

		// paint the axis unit
		g2.setColor(canvasSetting.xUnit.fontColor);
		g2.setFont(canvasSetting.xUnit.font);
		g2.setStroke(canvasSetting.xUnit.generateStroke());
		double deltaX = Math.abs(maxX - minX);
		int xPix;
		double x;
		int yUnit = py1 + fmUnitX.getHeight() * 3 / 4 + sizeAxisTick;
		for (int i = 0; i < nUnitX; i++) {
			x = minX + scaleX * i;
			xPix = px1 + (int) Math.round((px2 - px1) * (x - minX) / deltaX);

			g2.drawLine(xPix, py1, xPix, py1 + sizeAxisTick);
			String strX = formatterX.format(x);
			g2.drawString(strX, xPix - fmUnitX.stringWidth(strX) / 2, yUnit);
		}

		// paint the axis title
		g2.setColor(canvasSetting.xAxis.fontColor);
		g2.setFont(canvasSetting.xAxis.font);
		int xt = px1 + (px2 - px1 - fmTitleX.stringWidth(titleX)) / 2;
		int yt = d.height - fmTitleX.getHeight() / 4-5;
		g2.drawString(titleX, xt, yt);
	}

	private void paintAxisY(Graphics2D g2) {
		FontMetrics fmUnitY = g2.getFontMetrics(canvasSetting.yUnit.font);
		FontMetrics fmTitleY = g2.getFontMetrics(canvasSetting.yAxis.font);

		Dimension d = getSize();
		int axisH = d.height - sizeBorderBottom - sizeBorderTop;
		
		g2.setColor(canvasSetting.yAxis.lineColor);
		g2.setStroke(canvasSetting.yAxis.generateStroke());
		g2.drawLine(sizeBorderLeft, sizeBorderTop, sizeBorderLeft, d.height
				- sizeBorderBottom);

		g2.setFont(canvasSetting.yUnit.font);
		g2.setColor(canvasSetting.yUnit.fontColor);
		g2.setStroke(canvasSetting.yUnit.generateStroke());
		if (scaleTypeY == LOG_SCALE) {
			double deltaY = maxPowerY * 2;
			double scale = 1;// deltaY/10d;
			double y = -maxPowerY;
			int halfHeight = (int) (axisH / 2d);
			int yPix = 0;
			int uLine = sizeAxisTick;

			while (y <= maxPowerY) {
				int hMax = (showNegativeY && showPositiveY) ? halfHeight
						: axisH;

				yPix = (int) ((hMax) * (1 + y + maxPowerY) / (deltaY + 1));
				int yPixRel = Math.max(0, axisH - yPix);

				if (showPositiveY) {
					int uy1 = sizeBorderTop
							+ yPixRel
							- ((showNegativeY && showPositiveY) ? halfHeight
									: 0);
					g2.drawLine(sizeBorderLeft - uLine, uy1, sizeBorderLeft,
							uy1);
					String str = formatterY.format(Math.pow(10, y));
					int strX = sizeBorderLeft - fmUnitY.stringWidth(str)
							- uLine;
					g2.drawString(str, strX, uy1 + fmUnitY.getHeight() / 4);
				}
				if (showNegativeY) {
					int uy2 = sizeBorderTop
							+ axisH
							- yPixRel
							+ ((showNegativeY && showPositiveY) ? halfHeight
									: 0);
					g2.drawLine(sizeBorderLeft - uLine, uy2, sizeBorderLeft,
							uy2);
					String str = formatterY.format(Math.pow(10, y));
					str = "-" + str;
					int strX = sizeBorderLeft - fmUnitY.stringWidth(str)
							- uLine - 5;
					strX = sizeBorderLeft - fmUnitY.stringWidth(str) - uLine
							- 0;
					g2.drawString(str, strX, uy2 + fmUnitY.getHeight() / 4);
				}
				y += scale;
			}
		} else {
			double max = maxY;
			double min = minY;
			if (!showPositiveY) {
				max = 0;
			}
			if (!showNegativeY) {
				min = 0;
			}
			int px1 = sizeBorderLeft;
			int py1 = d.height - sizeBorderBottom;
			// int px2 = sizeBorderLeft;
			int py2 = sizeBorderTop;

			double deltaY = Math.abs(max - min);
			int yPix;
			double y;
			// int yUnit = py1 + fmUnitY.getHeight() * 3 / 4 + sizeAxisTick;
			int xUnit = 0;
			for (int i = 0; i < nUnitY; i++) {
				y = min + scaleY * i;
				if (y == 0) {
					if (isShowPositiveY()) {
						yPix = (int) (getCanvasY() + getCanvasHeight()
								/ (isShowNegativeLogY() && isShowPositiveY() ? 2
										: 1));
					} else {
						yPix = getCanvasY();
					}
				} else {
					yPix = py1
							+ (int) Math
									.round((py2 - py1) * (y - min) / deltaY);
				}
				g2.drawLine(px1 - sizeAxisTick, yPix, px1, yPix);

				String strY = formatterY.format(y);
				xUnit = sizeBorderLeft - fmUnitY.stringWidth(strY)
						- sizeAxisTick;

				g2.drawString(strY, xUnit, yPix + fmUnitY.getHeight() / 4);

			}
		}

		g2.setColor(canvasSetting.yAxis.fontColor);
		g2.setFont(canvasSetting.yAxis.font);
		if (titleY.equals("")) {
			titleY = MenuProperty.PROJECT_SUMMARY_OPCOST
					.getAccessibleDescription();
		}

		int titleWidth = fmTitleY.stringWidth(titleY);
		int xt = Math.max(0, ((int) axisH - titleWidth) / 2);

		AffineTransform at = new AffineTransform();
		at.translate(fmTitleY.getHeight() * 3 / 4+5, sizeBorderTop + axisH - xt);
		at.rotate(-Math.PI / 2);
		g2.transform(at);
		g2.drawString(titleY, 0, 0);
		try {
			at = at.createInverse();
		} catch (NoninvertibleTransformException e) {
			e.printStackTrace();
		}
		g2.transform(at);
	}

	void paintCanvas(Graphics2D g2, int x, int y, int width, int height) {

	}

	public void setCanvasSetting(CanvasFormat canvasSetting) {
		this.canvasSetting = canvasSetting;
	}

	public void setnUnitY(int nUnitY) {
		this.nUnitY = nUnitY;
	}

	public void setRangeX(double minX, double maxX) {
		nUnitX = 10;
		scaleX = (maxX - minX) / (double) nUnitX;
		// refine the scale
		if (scaleX > 0) {
			double rs = Math.pow(10, Math.round(Math.log10(scaleX)));
			rs = rs > scaleX ? rs / 10d : rs;
			scaleX = Math.round(scaleX / rs) * rs;
		} else {
			double rs = Math.pow(10, Math.round(Math.log10(Math.abs(scaleX))));
			rs = rs > Math.abs(scaleX) ? rs / 10d : rs;
			scaleX = -Math.round(Math.abs(scaleX) / rs) * rs;
		}
		double nMin = Math.floor(minX / scaleX);
		double nMax = Math.ceil(maxX / scaleX);
		this.minX = nMin * scaleX;
		this.maxX = nMax * scaleX;
		nUnitX = (int) (nMax - nMin) + 1;
	}

	public void setRangeY(double minY, double maxY) {
		this.minY = minY;
		this.maxPowerY = Math.ceil(Math.log10(Math.max(Math.abs(minY), maxY)));
		this.maxY = maxY;

		nUnitY = 10;
		scaleY = (maxY - minY) / (double) nUnitY;
		// refine the scale
		if (scaleY > 0) {
			double rs = Math.pow(10, Math.round(Math.log10(scaleY)));
			rs = rs > scaleY ? rs / 10d : rs;
			scaleY = Math.round(scaleY / rs) * rs;
		} else {
			double rs = Math.pow(10, Math.round(Math.log10(Math.abs(scaleY))));
			rs = rs > Math.abs(scaleY) ? rs / 10d : rs;
			scaleY = -Math.round(Math.abs(scaleY) / rs) * rs;
		}
		double nMin = Math.floor(minY / scaleY);
		double nMax = Math.ceil(maxY / scaleY);
		this.minY = nMin * scaleY;
		this.maxY = nMax * scaleY;
		nUnitY = (int) (nMax - nMin) + 1;
	}

	public void setScaleTypeX(int scaleTypeX) {
		this.scaleTypeX = scaleTypeX;
	}

	public void setScaleTypeY(int scaleTypeY) {
		this.scaleTypeY = scaleTypeY;
	}

	public void setShowNegativeLogY(boolean showNegativeLogY) {
		this.showNegativeY = showNegativeLogY;
	}

	public void setShowPositiveY(boolean showPositiveY) {
		this.showPositiveY = showPositiveY;
	}

	public void setTitleX(String titleX) {
		this.titleX = titleX;
	}

	public void setTitleY(String titleY) {
		this.titleY = titleY;
	}

	void updateBorderSize(Graphics2D g2) {
		// TODO: jadiin font metrics public field
		FontMetrics fmUnitX = g2.getFontMetrics(canvasSetting.xUnit.font);
		FontMetrics fmTitleX = g2.getFontMetrics(canvasSetting.xAxis.font);
		sizeBorderBottom = sizeAxisTick + fmUnitX.getHeight()
				+ fmTitleX.getHeight()+5;

		FontMetrics fmUnitY = g2.getFontMetrics(canvasSetting.yUnit.font);
		FontMetrics fmTitleY = g2.getFontMetrics(canvasSetting.yAxis.font);
		String strMax = formatterY.format(Math.pow(10, maxPowerY));
		sizeBorderLeft = fmTitleY.getHeight() + fmUnitY.stringWidth(strMax)
				+ sizeAxisTick + 10;
		sizeBorderTop = fmUnitY.getHeight() / 2;
		sizeBorderRight = fmUnitX.stringWidth(Float.toString((float) maxX));
	}
}
